/**
 *  函数式编程与JS 异步编程，手写promise
 * 简答题：
 * 如何理解js异步编程的? EventLoop 消息列队都是做什么的
 * 什么是宏任务，什么是微任务
 * Answer:
 *   js有两种任务模式，同步模式和异步模式，异步模式并不等待前一个任务结束后才执行下一个任务，开启就立即执行，一般通过回到函数来执行后续逻辑
 *    EventLoop：事件循环，监听调用栈和消息列队的，当调用栈中的任务都结束后，事件循环就会从消息列队中取第一个回调函数，压入到调用栈中。
 *    消息列队就是对调函数的列队，排队等待事件循环
 *    宏任务就是消息队列里的回调函数
 *    微任务：临时加上的额外需求，可以作为一个人的宏任务进到消息列队中，
 *           setTimeout可以作为当前任务的微任务，就是在当前任务结束后立即执行而不是重新排队，微任务就是为了提高整体的相应能力
 *           promise回调就是作为微任务去执行的，还有就是node中 nextTick 也都是微任务
 */


// 代码题

//  将下面异步代码使用promise的方式改进
setTimeout(() => {
  var a = 'hello ';
  setTimeout(() => {
    var b = 'lagou '
    setTimeout(() => {
      var c = 'i love u'
      console.log(a + b + c);
    }, 10)
  }, 10)
}, 10)

const myPromise = new Promise((resolve, reject) => {
  resolve('hello')
})
myPromise.then(() => {
  return b = 'lagou'
}).then(() => {
  return c = 'I ❤ U'
}).then(v => {
  console.log('I ❤ U', v)
})



// 二：基于代码完成练习
const fp = require('lodash/fp')
// horserpower:马力, dollar_vale:价格， in_stock 库存
const cars = [
  { name: 'ferrari FF', horserpower: 660, dollar_value: 700000, in_stock: true },
  { name: 'Spyker C12 Zagato', horserpower: 650, dollar_value: 648000, in_stock: false },
  { name: 'Jaguar XKR-S', horserpower: 550, dollar_value: 132000, in_stock: false },
  { name: 'Audi R8', horserpower: 525, dollar_value: 114200, in_stock: false },
  { name: 'Aston Martin One-77', horserpower: 750, dollar_value: 1850000, in_stock: true },
  { name: 'Pagani Huayra', horserpower: 700, dollar_value: 1300000, in_stock: false }
]

// 练习一：使用函数组合 fp.flowRight()重新实现下面这个函数
let isLastInStock = function (cars) {
  let last_car = fp.last(cars) // 取最后一条数据 获取该数据的in_stock属性值 fp.prop
  return fp.prop('in_stock', last_car)
}
const LastInStock2 = fp.flowRight(fp.prop('in_stock'), fp.last)
console.log('练习1', LastInStock2(cars))

// 练习二：使用fp.flowRight() fp.prop() fp.first() 获取第一个car的name
const getFirstName = fp.flowRight(fp.prop('name'), fp.first)
console.log('练习2', getFirstName(cars))

// 练习三：使用帮助函数 _average重构 averageDollarValue,使用函数组合的方式实现
let _average = function (xs) {
  return fp.reduce(fp.add, 0, xs) / xs.length
}
let averageDollarValue = (cars) => {
  return _average(fp.map((car) => {
    car.dollar_value
  }))
}

let averageDollarValue2 = fp.flowRight(_average, fp.map((car) => car.dollar_value))
console.log('练习3-1', averageDollarValue2(cars))
// 或者
function getResultArr(cars) {
  let valueArr = fp.map((car => car.dollar_value), cars)
  return valueArr
}

function compose(f2, f1) {
  return function (value) {
    return f2(f1(value))
  }
}
const last = compose(_average, getResultArr)
console.log('练习3-2', last(cars))

// 练习四：使用flowRight写一个 sanitizeNames() 函数，返回一个下划线连接的小写字符串
// 把数组中的name转换为这种形式，例如：sanitizeNames(['Hello World']) => ['hello_world]
const log = v => {
  console.log('看步骤', v)
  return v
}
var _ = require('lodash')
const trace = _.curry((tag, v) => {
  console.log(tag, v)
  return v
})
let _underscore = fp.replace(/\s+/g, '_') // 在 sanitizeNames函数中使用

let sanitizeNames = fp.flowRight(trace('转换为下划线'), fp.map(_underscore), trace('变小写'), fp.map(item => item.toLowerCase()))
let sanitizeNames2 = fp.map(fp.flowRight(_underscore, log, fp.toLower))
let sanitizeNames3 = fp.flowRight(fp.map(fp.flowRight(_underscore, fp.toLower)))
console.log('1 sanitizeNames', sanitizeNames(['Hello World', 'Hello Join']))
console.log('2 sanitizeNames2', sanitizeNames2(['Hello World', 'Hello Join']))
console.log('3 sanitizeNames3', sanitizeNames3(['Hello World', 'Hello Join']))

// 三：基于代码完成练习

/**
 class Maybe {
  static of(x) {
    return new Maybe(x)
  }
  isNothing() {
    return this._value === null || this._value === undefined
  }
  constructor(x) {
    this._value = x
  }
  map(fn) {
    return this.isNothing ? this : Maybe.of(fn(this._value))
  }
}
*/
// 1.使用fp.add(x,y)和fp.map(f,x)创建一个能让functor里的值增加的函数ex1
const { Maybe, Container } = require('./support')
let maybeValue = Maybe.of([5, 6, 1])

// let ex1 = (num) => {
//   return maybeValue.map(fp.map(item => fp.add(item, num)))
// }
let ex1 = (num) => {
  return maybeValue.map(item => fp.map(fp.add(num), item))
}
console.log(11, 'ex1', ex1(2))

// 2.实现一个函数ex2,能够使用 fp.first获取列表的第一个元素
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])

let temp = xs._value
// console.log(34567, temp)
let ex2 = () => xs.map(fp.fist)._value
console.log(22, 'ex2', ex2)

// 3.实现一个函数ex3，使用safeProp和fp.first找到user的名字的首字母
let safeProp = fp.curry(function (x, o) { return Maybe.of(o[x]) })
let user = { id: 2, name: "Albert" }
let ex3 = () => fp.flowRight(fp.map(fp.first), fp.split(''), safeProp('name'))
// let tempEx3 = safeProp('name', user)
// let tempName = tempEx3.map(x => fp.split('', x))
// console.log(123456, tempEx3, 123456, tempName)
console.log(33, 'ex3', ex3(user))

// 4.使用Maybe 重写ex4，不要有if语句
// let ex4 = function (n) {
//   if (n) { return parseInt(n) }
// }
let ex4 = fp.flowRight(fp.map(parseInt), Maybe.of)
console.log(44, 'ex4', ex4)
console.log(44, 'ex4', ex4(1.5)) // 1


// 四：手写实现MyPromise源码，尽可能还原Pormise中的每个API，并通过注释的 描述思路和原理
// 单独定义一个promise类的文件 导出方法  module.exports = PractisePromise
// 引用文件  const  PractisePromise = require(文件路径)
// const 常量 定义状态 3种实例属性, 方便后面多次调用
const PENDING = 'pending' // 等待状态
const FULFILLED = 'fulfilled' // 成功状态
const REJECTED = 'rejected' // 失败状态

class PractisePromise {
  constructor(executor) { // 传入执行器,执行两种回调
    // 捕获错误 在构造函数中调用执行器，所以这里可以捕获错误
    try {
      ececutor(this.resolve, this.reject) // this.fun 指向当前类的方法
    } catch (e) {
      this.reject(e)
    }
  }

  status = PENDING; // 定义初始状态 status
  value = undefined; // 定义成功后的值
  reason = undefined; // 定义失败后原因
  // successCallBack = undefined // 定义成功回调  单次调用
  successCallBack = [] // 定义成功回调 then 方法的多次调用  使用数组
  // failCallBack = undefined // 定义失败回调 单次调用
  failCallBack = []// 定义失败回调 单次调用

  // 更改状态的两个方法 resolve reject
  resolve = (valve) => { // 箭头函数 固定this的指向类
    if (this.status !== PENDING) return; // 如果已有 执行回调后的状态 则return
    this.status = FULFILLED
    this.value = valve // 保存成功回调的值
    // this.successCallBack && this.successCallBack(this.value) // 如果存在成功回调，把值传下去 ，单次调用
    // 多次调用，判断长度，每次取出来  重新把value传递下去
    // shift() 方法用于把数组的第一个元素删除，并返回第一个元素的值。
    // while (this.successCallBack.length) this.successCallBack.shift()(this.value)
    while (this.successCallBack.length) this.successCallBack.shift()() // 3中状态都有setTimeout这里就不需要传值了
  }

  reject = (reason) => {
    if (this.status !== PENDING) return; // 非等待状态 return
    this.status = REJECTED
    this.reason = reason // 保存失败后的原因
    this.failCallBack && this.failCallBack(this.reason) // 如果存在失败回调，保存原因 ，单次调用
    // while (this.failCallBack.length) this.failCallBack.shift()(this.reason)
    while (this.failCallBack.length) this.failCallBack.shift()()
  }

  then(successCallBack, failCallBack) {
    // 创建promise对象，通过return 把值传递给下一个promise对象
    let promise2 = new PractisePromise((resolve, reject) => {
      // 3中状态都写了setTimeout方法 捕获错误后，上面resolve方法就不在需要传值了
      if (this.status === FULFILLED) { // 也要判断状态
        /**
          // successCallBack(this.value)
          let x = successCallBack(this.value)
          // 判断x是普通值还是promise对象
          // 如果是普通值 直接调用 resolve
          // 如果是promise对象，查看promise对象返回的结果
          // 然后根据Promise对象返回的结果，决定是resolve还是reject回调
          // resolve(x) // 便于then方法的链式调用，把值传递给下一个promise对象
          // 这里可以写一个公共方法，方便随时用
          // resolvePromise(x, resolve, reject) // 判断x的值，决定调用哪个回调
          // 还不能自己return自己所以添加了  promise2和x的对比
          resolvePromise(promise2, x, resolve, reject) // promise2是new完之后才有的，在new的过程中，是获取不到的
          // 如果Promise2和x相等，那就是promise对象
        */

        // 传递promise2  改异步方式 内部添加 setTimeout()，但时间改为0
        // setTimeout(() => {
        //   let x = successCallBack(this.value)
        //   resolvePromise(promise2, x, resolve, reject)
        // }, 0);
        // 捕获错误 then 方法内部获取错误，传递给下一个then方法

        //setTimeout里面添加 try  catch
        setTimeout(() => {
          try {
            let x = successCallBack(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
        // 调用文件 同样用 throw new Error('') 抛出错误

      } else if (this.status === REJECTED) {
        // failCallBack(this.reason)
        /** 失败回调的错误捕获同成功回调
          // 调用文件 同样用 throw new Error('') 抛出错误
        */
        setTimeout(() => {
          try {
            let x = failCallBack(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else { // 等待状态 存储成功回调  存储失败回调
        // this.successCallBack = successCallBack // 单次调用
        // this.failCallBack = failCallBack // 单次调用
        // this.failCallBack.push(failCallBack) // then方法多次调用 的写法
        // this.failCallBack.push(failCallBack) // 多次调用
        // 当异步状态时 回调直接push  对错误捕获无法进行处理，改为箭头函数形式，在push的函数里调用成功回调
        this.successCallBack.push(() => {
          // successCallBack();
          // 把setTimeout的成功回调放在这里，就可以处理了
          setTimeout(() => {
            try {
              let x = successCallBack(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.failCallBack.push(() => {
          // failCallBack();
          setTimeout(() => {
            try {
              let x = failCallBack(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    // 创建好promise2后，return出去，这样链式调用的就是前一个promise对象 
    return promise2
  }
}

// function resolvePromise(x, resolve, reject) {
function resolvePromise(promise2, x, resolve, reject) { // 这里取不到  promise2
  // 判断promise2, x 是否相等
  if (promise2 === x) {
    return reject(new TypeError('promise内部循环，报错'))
  }
  if (x instanceof PractisePromise) {
    x.then(resolve, reject) // 传promise对象
  } else {
    resolve(x) // 传值下去
  }
}

// 调用文件 new 引入文件里定义的类名
// const PractisePromise = require(文件路径)
let promiseTemp = new PractisePromise((resolve, reject) => {
  resolve('success')
  // reject('error') // 状态一旦更改就不可逆，所以这里注释  二选一
  // 类的构造函数有捕获错误的方法 这里用 throw new Error('') 放resolve 前面

})

promiseTemp.then(value => {
  console.log(value)
}, reason => {
  console.log(reason)
})

// 多次then调用  可成功打印三次
promiseTemp.then(value => {
  console.log(1)
  console.log(value)
});

promiseTemp.then(value => {
  console.log(2)
  console.log(value)
});

promiseTemp.then(value => {
  console.log(3)
  console.log(value)
});

// then方法的多次链式调用, then方法
// function other() {
//   return new PractisePromise((resolve, reject) => {
//     resolve('other-other')
//   })
// }

promiseTemp.then(value => {
  console.log(value);
  // return 100; // 100可传递

  // return other() // 返回一个promise对象，不能return当前then方法的返回对象，会发生promise循环调用，报错，chaining cycle detected for promise #<Promise>
}).then(value => {
  console.log(value) // 这里就是传递来的100
})

